<!doctype html>
<html lang="en">
<head>

<meta charset="utf-8">
<title>alchi. big five to four elements</title>

<script>

// big five to alchi

// M: maximum value. 1 or 100
function elementOfBigfive({ o, c, e, a, n }, M=1) {
  /*
  // formula 1
  return {
    e1: (   o  +    e  + (M-n) +    c  + (M-a))/5,
    e2: ((M-o) + (M-e) +    n  + (M-c) + (M-a))/5,
    e3: (   o  +    e  +    n  + (M-c) +    a )/5,
    e4: ((M-o) + (M-e) + (M-n) +    c  +    a )/5,
  };
  */

  // formula 2
  return {
    e1: (   o  +    e  + (M-n) +    c  + 2*(M-a))/6,
    e2: ((M-o) + (M-e) +    n  + (M-c) + 2*(M-a))/6,
    e3: (   o  +    e  +    n  + (M-c) + 2*   a )/6,
    e4: ((M-o) + (M-e) + (M-n) +    c  + 2*   a )/6,
  };
}

const oppositeElement = {
  e1: 'e2',
  e2: 'e1',
  e3: 'e4',
  e4: 'e3',
};

function modalityOfElements(baseElement, secondElement) {
  if (baseElement == secondElement) return 'mutable';
  if (secondElement == oppositeElement[baseElement]) return 'forbidden';
  // cardinal = x plays a childish element (fire or air)
  // fixed = x plays a mature element (earth or water)
  if (secondElement == 'e1' || secondElement == 'e3') return 'cardinal';
  return 'fixed';
}

const nameOfMBTI = {
  e1: { e1: 'INTP', e2: 'INFJ', e3: 'INFP', e4: 'INTJ' },
  e2: { e1: 'ESTP', e2: 'ESFJ', e3: 'ESFP', e4: 'ESTJ' },
  e3: { e1: 'ENTP', e2: 'ENFJ', e3: 'ENFP', e4: 'ENTJ' },
  e4: { e1: 'ISTP', e2: 'ISFJ', e3: 'ISFP', e4: 'ISTJ' },
};

const nameOfZodiac = {
  e1: { e1: 'Sagittarius', e2: 'forbidden', e3: 'Aries', e4: 'Leo' },
  e2: { e1: 'forbidden', e2: 'Virgo', e3: 'Capricorn', e4: 'Taurus' },
  e3: { e1: 'Libra', e2: 'Aquarius', e3: 'Gemini', e4: 'forbidden' },
  e4: { e1: 'Cancer', e2: 'Scorpius', e3: 'forbidden', e4: 'Pisces' },
};

const nameOfDiagonal = {
  '12': 'fire-earth',
  '34': 'air-water',
};

function zodiacOfElements(baseElement, secondElement) {
  return nameOfZodiac[baseElement] && nameOfZodiac[baseElement][secondElement];
}

function mbtiOfElements(baseElement, secondElement) {
  return nameOfMBTI[baseElement] && nameOfMBTI[baseElement][secondElement];
}

function diagonalOfAgree(agree) {
  if (agree > 50) return 'air-water';
  if (agree < 50) return 'fire-earth';
  if (agree == 50) return undefined;
}

const cardinalElementOfDiagonal = { '12': 'e3', '34': 'e1' };
const fixedElementOfDiagonal = { '12': 'e4', '34': 'e2' };

// round
function num(n, digits = 0) {
  if (n == undefined) return 'undefined';
  return parseFloat(n.toFixed(digits));
  //return Math.round(n);
}

function getInterpretation(bigfive, elementObject) {
  const { o, c, e, a, n } = bigfive;
  let res = '';
  const elementList = Object.keys(elementObject).map(elm => [elm, elementObject[elm]]);
  elementList.sort((a, b) => b[1] - a[1]); // sort descending

  let range;
  let rangeStrong;
  let rangeWeak;
  let fireScore;
  let airScore;
  let virtualAgree;
  let diagonalSign;
  let diagonal;
  let baseElement;

  const rangeMinimum = 10;

  function ambiguousInterpretation(bigfive, reason = '') {
    const { o, c, e, a, n } = bigfive;

    // TODO verify: result is ambiguous. this is a sign of element earth or element water.

    return `\
result is ambiguous.${!reason ? '' : `  \n${reason}.`}

we expect these correlations:

* high O = high E
* high C = low N
* high A = air (high OEN + low C) or water (low OEN + high C)

generated by [bigfive2alchi.html](https://milahu.github.io/alchi/src/bigfive2alchi.html#ocean=${o}.${c}.${e}.${a}.${n})

\\--

debug:

* range = ${num(range)}
* rangeStrong = ${num(rangeStrong)}
* rangeWeak = ${num(rangeWeak)}
* fireScore = ${num(fireScore)}
* airScore = ${num(airScore)}
* virtualAgree = ${num(virtualAgree)}
* diagonalSign = ${diagonalSign}
* diagonal = ${diagonal}
`;
  }

  range = elementList[0][1] - elementList[3][1];
  if (range < rangeMinimum) {
    return ambiguousInterpretation(bigfive, `strongest and weakest element are too close together (range = ${num(range, 1)} < ${rangeMinimum})`);
  }

  rangeStrong = elementList[0][1] - elementList[2][1];
  rangeWeak = elementList[1][1] - elementList[3][1];

  // score vs opposite element
  fireScore = elementObject.e1 - elementObject.e2;
  airScore = elementObject.e3 - elementObject.e4;

  //const virtualAgree = 50 + 5/8*(Math.abs(airScore) - Math.abs(fireScore)); // formula 1
  virtualAgree = (200/3 + (Math.abs(airScore) - Math.abs(fireScore)))/(4/3); // formula 2

  const diagonalTolerance = 10;
  //const diagonalSign = Math.sign(Math.abs(fireScore) - Math.abs(airScore));
  diagonalSign = Math.sign(
    Math.round((
      Math.abs(fireScore) - Math.abs(airScore)
    ) * diagonalTolerance) / diagonalTolerance
  );

  const diagonalMap = { '-1': '34', '0': '?', '1': '12' };
  diagonal = diagonalMap[diagonalSign];

  const baseElementFromDiagonal =
    diagonal == '?' ? '?' :
    diagonal == '12'
    ? ( fireScore == 0 ? '?' : fireScore > 0 ? 'e1' : 'e2' )
    : ( airScore == 0 ? '?' : airScore > 0 ? 'e3' : 'e4' )
  ;
  baseElement = baseElementFromDiagonal;

  const cardinalElement = cardinalElementOfDiagonal[diagonal];
  const fixedElement = fixedElementOfDiagonal[diagonal];

  const modalityRange = elementObject[cardinalElement] - elementObject[fixedElement];
  const modality = Math.abs(modalityRange) < 5 ? 'mutable'
    : modalityRange > 0 ? 'cardinal' : 'fixed';
  const modalityElement =
    modality == 'mutable' ? baseElement :
    modality == 'cardinal' ? cardinalElement : fixedElement
  ;

  res += `\
\\--

derivation: element and modality:

* diagonalRange: ${num(fireScore, 2)} fire-earth + ${num(airScore, 2)} air-water
* element ${nameOfElement[baseElementFromDiagonal]}
* valid modalities (cardinal mutable fixed): ${nameOfElement[cardinalElement]} ${nameOfElement[baseElement]} ${nameOfElement[fixedElement]}
* modalityRange: ${num(modalityRange, 2)} ${nameOfElement[cardinalElement]}-${nameOfElement[fixedElement]}
* ${modality} modality
* ${nameOfElement[baseElementFromDiagonal]} plays ${nameOfElement[modalityElement]}

verification: virtual diagonal, derived from O C E N:

* virtualAgree ${num(virtualAgree)}
* virtualDiagonal ${nameOfDiagonal[diagonal]}

\\--

debug:

* range = ${num(range)}
* rangeStrong = ${num(rangeStrong)}
* rangeWeak = ${num(rangeWeak)}
* fireScore = ${num(fireScore)}
* airScore = ${num(airScore)}
* virtualAgree = ${num(virtualAgree)}
* diagonalSign = ${diagonalSign}
* diagonal = ${diagonal}
`;



  if (Math.sign(virtualAgree - 50) != Math.sign(bigfive.a - 50)) {
    res += `* contradiction: agree ${bigfive.a} + virtualAgree ${num(virtualAgree)}\n`;
  }
  else {
    res += `* symmetry: agree ${bigfive.a} + virtualAgree ${num(virtualAgree)}\n`;
  }

  //console.log(`baseElement ${baseElement}`);
  if (baseElement == '?') {
    return ambiguousInterpretation(bigfive, 'base element was not found');;
  }
  else {

    // prepend to res
    res = `\
short: ${nameOfElement[baseElement]} plays ${nameOfElement[modalityElement]}

translations:

* ${mbtiOfElements(baseElement, modalityElement)} in MBTI
* ${zodiacOfElements(baseElement, modalityElement)} = ${modalityOfElements(baseElement, modalityElement)} ${nameOfElement[baseElement]} in zodiac (calendar-astrology is fake)
* ${carlJungNameOfModality[modality]} ${carlJungNameOfElement[baseElement]} in Carl Jung

generated by [bigfive2alchi.html](https://milahu.github.io/alchi/src/bigfive2alchi.html#ocean=${o}.${c}.${e}.${a}.${n})

\\--
${modality != 'mutable' ? `
note: verbal tests (questionnaires) easily confuse element and modality,
so element and modality can be swapped:

* ${nameOfElement[modalityElement]} plays ${nameOfElement[baseElement]}
* ${mbtiOfElements(modalityElement, baseElement)}
* ${zodiacOfElements(modalityElement, baseElement)}
* ${modalityOfElements(modalityElement, baseElement)} ${nameOfElement[modalityElement]}

to find your element, try to find your body type:

* heart-shape &rarr; fire
* pear-shape &rarr; earth
* endo-morph &rarr; air
* ecto-morph &rarr; water
` : ''}
element ${nameOfElement[baseElement]} translations:

* ${keirseyNameOfElement[baseElement]} in David Keirsey
* ${sheldonNameOfElement[baseElement]} in William Sheldon
* ${dressColorOfElement[baseElement]} in alchi dress color
* ${southParkNameOfElement[baseElement]} in South Park
* ${harryPotterNameOfElement[baseElement]} in Harry Potter
* ${healthtypeNameOfElement[baseElement]} in ph360.me (health type)
* ${flowprofileNameOfElement[baseElement]} in flowgenomeproject (flow profile)

` + res;

  }

/*

[ note: verbal tests (questionnaires) easily confuse element and modality, ]

... or element and modality show your two modalities (cardinal and fixed).

TODO verify

*/

  return res;
}

const nameOfElement = {
  e1: 'fire',
  e2: 'earth',
  e3: 'air',
  e4: 'water',
};

const keirseyNameOfElement = {
  e1: 'Artisan',
  e2: 'Guardian',
  e3: 'Idealist',
  e4: 'Rational',
};

const carlJungNameOfElement = {
  e1: 'Ntuiting',
  e2: 'Sensing',
  e3: 'Feeling',
  e4: 'Thinking',
};

const carlJungNameOfModality = {
  'cardinal': 'extraverted',
  'mutable': 'ambiverted',
  'fixed': 'introverted',
};

const sheldonNameOfElement = {
  e1: 'heart-shape mesomorph',
  e2: 'pear-shape mesomorph',
  e3: 'hourglass-shape endomorph',
  e4: 'rhombus-shape ectomorph',
};

const dressColorOfElement = {
  e1: 'yellow top + blue bottom',
  e2: 'blue bottom + yellow top',
  e3: 'red top + green bottom',
  e4: 'green top + red bottom',
};

const southParkNameOfElement = {
  e1: 'Kenny',
  e2: 'Stan',
  e3: 'Eric',
  e4: 'Kyle',
};

const harryPotterNameOfElement = {
  e1: 'Gryffindor',
  e2: 'Hufflepuff',
  e3: 'Ravenclaw',
  e4: 'Slytherin',
};

const healthtypeNameOfElement = {
  e1: 'Crusader',
  e2: 'Guardian',
  e3: 'Connector',
  e4: 'Sensor',
};

const flowprofileNameOfElement = {
  e1: 'Hard Charger',
  e2: 'Flow Goer',
  e3: 'Crowd Pleaser',
  e4: 'Deep Thinker',
};



function sortedElements(elementObject) {
  const elementList = Object.keys(elementObject).map(elm => [elm, elementObject[elm]]);
  elementList.sort((a, b) => b[1] - a[1]); // sort descending
  return elementList.map(([elm, val]) => `${num(val)} ${nameOfElement[elm]}`).join(' + ');
}

function handle_form_change(form) {
  //console.dir(form);
  const bigfive = Array.from(form).reduce((acc, input) => {
    acc[input.name] = input.valueAsNumber;
    document.getElementById(input.name).innerHTML = input.value;
    return acc;
  }, {});

  const elements = elementOfBigfive(bigfive, 100);
  const { o, c, e, a, n } = bigfive;
  const { e1, e2, e3, e4 } = elements;
  //console.log(`o ${o} + c ${c} + e ${e} + a ${a} + n ${n} -> e1 ${e1} + e2 ${e2} + e3 ${e3} + e4 ${e4}`);

  // set URL
  document.location.hash = `#ocean=${o}.${c}.${e}.${a}.${n}`;
  
  //document.getElementById('result').innerHTML = `o ${o} + c ${c} + e ${e} + a ${a} + n ${n} &rarr; e1 ${e1} + e2 ${e2} + e3 ${e3} + e4 ${e4}`;
  //document.getElementById('result').innerHTML = `${o} open + ${c} conscient + ${e} extravert + ${a} agree + ${n} neuro &rarr; ${e1} fire + ${e2} earth + ${e3} air + ${e4} water`;

//${o} open + ${c} conscient + ${e} extravert + ${a} agree + ${n} neuro &nbsp;
//ocean ${o}.${c}.${e}.${a}.${n} &rarr; ${sortedElements(elements)}

  document.getElementById('result').innerHTML = `
<pre>
ocean ${o} ${c} ${e} ${a} ${n} &rarr; ${sortedElements(elements)}

${getInterpretation(bigfive, elements)}
</pre>
  `;

  //console.dir({ bigfive });
  const outform = document.getElementById('output');
  Array.from(outform).forEach(input => {
    //console.dir({ input: { name: input.name, value: input.value, elm: input.name[1] } });
    input.value = elements[input.name];
    document.getElementById(input.name).innerHTML = input.value;
  });
}

const bigfiveOfElement = {
  e1: { o: 100, c: 100, e: 100, a:   0, n:   0 },
  e2: { o:   0, c:   0, e:   0, a:   0, n: 100 },
  e3: { o: 100, c:   0, e: 100, a: 100, n: 100 },
  e4: { o:   0, c: 100, e:   0, a: 100, n:   0 },
};


function setInput(bigfive) {
  const inputForm = document.getElementById('input');
  Array.from(inputForm).forEach(input => {
    input.value = bigfive[input.name];
  });
  inputForm.dispatchEvent(new Event('change'));
}

function e(elm) {
  const bigfive = bigfiveOfElement['e'+elm];
  setInput(bigfive);
}

</script>

<style>
  body {
    font-family: sans-serif;
  }
  #main {
    display: flex;
  }
  form span {
    display: inline-block;
    width: 2em;
    position: relative; top: -3px; /* workaround: range and span on same middle line */
  }
  form span[id] {
    display: inline-block;
    width: 1.8em;
    text-align: right;
  }
  input[type="range"] {
    margin-right: 0.5em;
  }
  pre {
    /*white-space: pre-line;*/
  }
  #result {
    font-family: sans-serif;
    border: solid 1px grey;
    padding: 0 0.5em;
  }
  a[onclick] {
    cursor: pointer;
  }
  a[onclick]:hover {
    text-decoration: underline;
  }
  /*
  * { border: solid 1px red; margin: 1px; }
  */
</style>

</head>

<body>

<div style="margin-bottom: 0.5em;">alchi: translate "the big five" to four elements</div>

<div id="main">

  <div>
    <div style="margin-bottom: 0.5em;">Big Five</div>
    <form id="input" onchange="handle_form_change(this)">
      <div><input name="o" type="range" min="0" max="100" step="5"><span>O = </span><span id="o">50</span></div>
      <div><input name="c" type="range" min="0" max="100" step="5"><span>C = </span><span id="c">50</span></div>
      <div><input name="e" type="range" min="0" max="100" step="5"><span>E = </span><span id="e">50</span></div>
      <div><input name="a" type="range" min="0" max="100" step="5"><span>A = </span><span id="a">50</span></div>
      <div><input name="n" type="range" min="0" max="100" step="5"><span>N = </span><span id="n">50</span></div>
    </form>
  </div>

  <div style="padding-left: 2em;">
    <div style="margin-bottom: 0.5em;">Four Elements (<a onclick="e(1)">fire</a> <a onclick="e(2)">earth</a> <a onclick="e(3)">air</a> <a onclick="e(4)">water</a>)</div>
    <form id="output">
      <div><input name="e1" type="range" min="0" max="100" disabled><span>1 = </span><span id="e1">50</span></div>
      <div><input name="e2" type="range" min="0" max="100" disabled><span>2 = </span><span id="e2">50</span></div>
      <div><input name="e3" type="range" min="0" max="100" disabled><span>3 = </span><span id="e3">50</span></div>
      <div><input name="e4" type="range" min="0" max="100" disabled><span>4 = </span><span id="e4">50</span></div>
    </form>
  </div>

</div>

<div id="result" style="margin-top: 1em;"></div>

<script>

// add live update
const inputForm = document.getElementById('input');
Array.from(inputForm).forEach(input => {
  input.oninput = (event) => {
    const input = event.target;
    const label = document.getElementById(input.name);
    label.innerHTML = input.value;
  };
});

// onload: parse URL
const m = document.location.hash.match(/^#ocean=(\d+)\.(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
if (m) {
  const [ _, o, c, e, a, n ] = m;
  const bigfive = { o, c, e, a, n };
  setInput(bigfive);
} else {
  // init result
  console.log('init result');
  handle_form_change(document.getElementById('input'));
}

</script>

</body>
</html>
